Перейти к основному содержимому

3.04. Справочник по эмодзи

Разработчику Аналитику Тестировщику
Архитектору Инженеру

Справочник по эмодзи

📚 Часть 1: Основы стандарта, свойства и классификация

1. Что такое «эмодзи» с точки зрения стандарта Unicode

Слово emoji (絵文字) — японское заимствование: e («рисунок») + moji («знак, символ»). Однако в контексте IT эмодзи — не отдельный набор символов, а надмножество графем Unicode, обладающих специальными свойствами и обработкой.

Согласно Unicode Technical Standard #51 (UTS #51), версия 15.1 (2024), эмодзи определяются не просто как «картинки», а как:

«Символы, которые используются для выражения идей, эмоций, объектов или концепций посредством графического представления, и которые включены в официальную таблицу Emoji в рамках стандарта Unicode.»

Важно: не каждый пиктографический символ — эмодзи. Например, символы Dingbats (U+2700–U+27BF, Dingbats) формально не являются эмодзи, хотя визуально могут быть похожи (✔, ✘, ✏), если только они не включены в официальный список Emoji=Yes.


2. Основные свойства эмодзи в Unicode

Каждому символу Unicode может быть сопоставлен ряд свойств. Для эмодзи наиболее важны:

СвойствоТипВозможные значенияОписание
EmojiBooleanYes / NoЯвляется ли символ базовым эмодзи (см. Emoji=Yes в emoji-data.txt)
Emoji_PresentationBooleanYes / NoПо умолчанию отображается как изображение (не как текстовый глиф)
Emoji_ModifierBooleanYes / NoЯвляется модификатором (например, селекторы: U+1F3FB–U+1F3FF)
Emoji_Modifier_BaseBooleanYes / NoК какому символу применимы модификаторы (например, 👋, 👩)
Emoji_ComponentBooleanYes / NoЯвляется частью составной последовательности (включая ZWJ-sequences)
Extended_PictographicBooleanYes / NoРасширенное пиктографическое поведение (используется для определения word boundaries, например, в ICU)

⚠️ Важно:

  • Emoji=Yes ≠ «отображается как картинка». Для этого необходимо Emoji_Presentation=Yes.
  • Например, # (U+0023 NUMBER SIGN) имеет Emoji=Yes, но Emoji_Presentation=No. Чтобы его показать как эмодзи (#️⃣), требуется секвенция: U+0023 U+FE0F U+20E3.

3. Типы эмодзи и их структура

3.1. Базовые эмодзи (Basic Emoji)

  • Один кодпойнт, имеющий Emoji=Yes и, как правило, Emoji_Presentation=Yes.
  • Примеры:
    • 😀 U+1F600 GRINNING FACE
    • 🐍 U+1F40D SNAKE
    • 🚀 U+1F680 ROCKET

3.2. Эмодзи с вариантом отображения (Variation Sequences)

  • Базовый символ + Variation Selector-16 (U+FE0F) — чтобы принудительно включить emoji-рендеринг.
  • Примеры:
    • ©©️ (U+00A9 U+FE0F)
    • ##️⃣ (U+0023 U+FE0F U+20E3 — keycap sequence)
    • **️⃣ (U+002A U+FE0F U+20E3)

🔍 Примечание:

  • U+FE0E (VS-15) — text presentation, т.е. отключает emoji-стиль. Например: ❤︎ (U+2764 U+FE0E) — сердце как текстовый символ.
  • Не все шрифты поддерживают VS-15/16 — поведение может варьироваться.

3.3. Keycap Sequences («клавиатурные эмодзи»)

Формат: [0-9#*] + U+FE0F + U+20E3 (COMBINING ENCLOSING KEYCAP).

ЭмодзиКодПримечание
0️⃣U+0030 U+FE0F U+20E3
1️⃣U+0031 U+FE0F U+20E3
#️⃣U+0023 U+FE0F U+20E3
*️⃣U+002A U+FE0F U+20E3

Не путать с цифрами в окружении: ① (U+2460) — это circled digit one, не эмодзи (Emoji=No).

3.4. Skin Tone Modifiers (Fitzpatrick Scale)

  • Модификаторы от U+1F3FB до U+1F3FF (Light to Dark Skin Tone).
  • Применяются только к символам с Emoji_Modifier_Base=Yes.
  • Образуют графемный кластер: [Base] + [Modifier]
МодификаторНазваниеПример (с 👋)
U+1F3FBLight Skin Tone👋🏻
U+1F3FCMedium-Light👋🏼
U+1F3FDMedium👋🏽
U+1F3FEMedium-Dark👋🏾
U+1F3FFDark👋🏿

✅ Проверка совместимости:

  • person (U+1F9D1), woman (U+1F469), man (U+1F468), hand (U+1F590, U+270B), nail polish (U+1F485) и др.
  • Не все базовые эмодзи поддерживают модификаторы (например, 🐶 — нет).

3.5. ZWJ-sequences (Zero Width Joiner)

  • Составные эмодзи, объединённые через U+200D (ZWJ).
  • Порядок важен: woman + ZWJ + heart + ZWJ + womanwoman + ZWJ + woman + ZWJ + heart.

Классические типы ZWJ-sequences:

ТипПримерПоследовательность (hex)
Семья👨‍👩‍👧‍👦1F468 200D 1F469 200D 1F467 200D 1F466
Профессия👩‍⚕️1F469 200D 2695 FE0F
Пара с сердцем👩‍❤️‍💋‍👨1F469 200D 2764 FE0F 200D 1F48B 200D 1F468
Люди с атрибутами🧑‍🦰1F9D1 200D 1F9B0 (red hair)

⚠️ Особенности:

  • Внутри ZWJ-sequences могут быть вложенные variation selectors (например, ⚕️ = 2695 FE0F).
  • Если рендерер не поддерживает полную последовательность — она «распадается» на отдельные эмодзи (fallback).
  • Для однозначной идентификации используется Emoji Sequence ID из emoji-sequences.txt.

3.6. Флаги (Flags)

Два вида:

  1. Государственные флаги — через Regional Indicator Symbols (U+1F1E6–U+1F1FF, A–Z).

    • 🇷🇺 = U+1F1F7 (R) + U+1F1FA (U)
    • Поддерживаются только для кодов ISO 3166-1 alpha-2, и только если платформа их реализует.
    • Например, XX (U+1F1FD U+1F1FD) — не отображается как флаг («non-flag»).
  2. Поднятый флаг (🏴, 🏁, 🎌) — базовые эмодзи, могут комбинироваться с цветами через ZWJ:

    • 🏴󠁧󠁢󠁥󠁮󠁧󠁿 = U+1F3F4 [E1100-E1163] U+E007F (subdivision flag)
    • Реализовано через Tag Sequence:
      🏴 + U+E0067 (g) + U+E0062 (b) + U+E0065 (e) + U+E006E (n) + U+E0067 (g) + U+E007F (cancel tag)

📌 Subdivision flags (подфлаги регионов) — крайне фрагментарны в поддержке (работают только в iOS/macOS, частично в Android ≥12).


4. Официальная классификация эмодзи (Unicode 15.1)

Все эмодзи разбиты на 11 групп и ~150 подгрупп. Примеры:

ГруппаПодгруппыКол-во эмодзи (Unicode 15.1)
Smileys & Emotionface-smiling, face-affection, face-tongue, hand-fingers-open, heart183
People & Bodyperson, person-gesture, person-activity, person-fantasy, family, person-symbol422
Animals & Natureanimal-mammal, animal-bird, plant-flower150
Food & Drinkfood-fruit, food-vegetable, drink123
Travel & Placesplace-map, place-religious, building, hotel, time218
Activitiessport, game, music, party, award-medal94
Objectsclothing, sound, tool, science, medical228
Symbolstransport-sign, warning, arrow, religion, zodiac208
Flagsflag, subdivision-flag270
Componentskin-tone, hair-style, gender12
Итого базовых + sequences3782

📊 По состоянию на Unicode 15.1:

  • Базовых символов с Emoji=Yes: 1424
  • Полных последовательностей (включая ZWJ, keycap, flags): 3782
  • Уникальных графем (emoji grapheme clusters): ≈ 4200+ (из-за combinatorics skin/gender/ZWJ)

📚 Часть 2: Данные стандарта — emoji-data.txt, emoji-sequences.txt, emoji-variation-sequences.txt

Эта часть посвящена технической документации Unicode, используемой разработчиками для корректной обработки эмодзи: как идентифицировать, валидировать, нормализовать и классифицировать эмодзи в коде. Акцент — на форматах .txt, предоставляемых официально на unicode.org.


1. Основные файлы данных (Unicode 15.1)

ФайлURL (15.1)Назначение
emoji-data.txtemoji-data.txtСвойства Emoji, Emoji_Presentation, Emoji_Modifier, Emoji_Modifier_Base, Emoji_Component, Extended_Pictographic для отдельных кодпойнтов
emoji-sequences.txtemoji-sequences.txtВсе официальные составные последовательности: ZWJ, family, skin tone, flags, subdivision flags
emoji-variation-sequences.txtemoji-variation-sequences.txtVariation sequences: [base] + FE0E/FE0F, keycaps (*#0-9 + FE0F + 20E3)
emoji-test.txtemoji-test.txtТестовые данные для валидации рендерера: status, group/subgroup, CLDR short names, sequences для всех уровней поддержки (fully-qualified, minimally-qualified, unqualified)

⚠️ Эти файлы — единственный нормативный источник для определения, является ли последовательность «официальным эмодзи». Любая ad-hoc комбинация (например, 🐶+🔥) — не эмодзи, даже если визуально склеивается в некоторых шрифтах.


2. Формат emoji-data.txt

Каждая строка — либо диапазон (XXXX..YYYY), либо одиночный кодпойнт (XXXX), затем ; и список свойств.

Примеры строк:

1F600          ; Emoji                # E0.6   😀  grinning face
1F601 ; Emoji # E0.6 😁 grinning face with smiling eyes
1F602 ; Emoji # E0.6 😂 face with tears of joy
231A..231B ; Emoji # E0.6 ⌚ ⌛ watch, hourglass
231A ; Emoji_Presentation # E0.6 ⌚ watch
231B ; Emoji_Presentation # E0.6 ⌛ hourglass
1F466..1F469 ; Emoji # E0.6 👦 👧 👨 👩 boy, girl, man, woman
1F466 ; Emoji_Modifier_Base # E0.6 👦 boy
1F467 ; Emoji_Modifier_Base # E0.6 👧 girl
1F468 ; Emoji_Modifier_Base # E0.6 👨 man
1F469 ; Emoji_Modifier_Base # E0.6 👩 woman
1F3FB..1F3FF ; Emoji_Modifier # E1.0 🏻 🏼 🏽 🏾 🏿 emoji modifier Fitzpatrick type-1-2..6
1F9B0..1F9B3 ; Emoji_Component # E11.0 🦰 🦱 🦲 🦳 red hair, curly hair, bald, white hair

Структура поля:

<codepoint(s)> ; <property> # <emoji_version> <sample> <name>
  • <emoji_version> — версия Unicode, в которой свойство было введено (не путать с Unicode Core version!). Например, E15.1 = Unicode 15.1 Emoji.
  • <sample> — примерный глиф (может не совпадать с вашим рендером).
  • <name> — официальное имя по стандарту.

📌 Примечание:
Свойства независимы. Один кодпойнт может иметь несколько:
U+1F469 (👩):

  • Emoji = Yes
  • Emoji_Presentation = Yes
  • Emoji_Modifier_Base = Yes
  • Emoji_Component = No

3. Формат emoji-variation-sequences.txt

Определяет variation sequences, влияющие на визуальное представление.

Формат:

<base> FE0F ; <name> # <status>

Примеры:

0023 FE0F        ; number sign       # fully-qualified
002A FE0F ; asterisk # fully-qualified
0030 FE0F ; digit zero # fully-qualified
...
2695 FE0F ; staff of Aesculapius # fully-qualified (⚕️)
2696 FE0F ; scale # fully-qualified (⚖️)
2708 FE0F ; airplane # fully-qualified (✈️)

🔍 Особые случаи:

  • Keycap sequences — записаны отдельно, т.к. требуют 3 кодпойнта:
    0030 FE0F 20E3 ; keycap: 0        # fully-qualified
    0031 FE0F 20E3 ; keycap: 1 # fully-qualified
    ...
    0023 FE0F 20E3 ; keycap: # # fully-qualified
    002A FE0F 20E3 ; keycap: * # fully-qualified
  • Статус minimally-qualified означает, что FE0F можно опустить, но тогда рендеринг может быть текстовым (например, vs ⚕️).

4. Формат emoji-sequences.txt

Содержит все официальные составные эмодзи. Четыре типа:

ТипПримерФормат записи
Basic Emoji + Skin Tone👋🏽1F44B 1F3FD
ZWJ Sequence👩‍💻1F469 200D 1F4BB
Flag🇷🇺1F1F7 1F1FA
Subdivision Flag🏴󠁧󠁢󠁥󠁮󠁧󠁿1F3F4 E0067 E0062 E0065 E006E E0067 E007F

Структура строки:

<sequence> ; <type> ; <CLDR name> # <status>

Примеры:

1F469 200D 1F3A4                       ; ZWJ ; woman singer                       # fully-qualified
1F468 200D 1F469 200D 1F466 ; ZWJ ; man, woman, boy # fully-qualified (семья)
1F468 1F3FB 200D 1F469 1F3FE 200D 1F466 1F3FF ; ZWJ ; man, woman, boy: light, medium-dark, dark skin tones # fully-qualified
1F1F7 1F1FA ; RGI_Emoji_Flag_Sequence ; flag: Russian Federation # fully-qualified
1F3F4 E0067 E0062 E0065 E006E E0067 E007F ; RGI_Emoji_Tag_Sequence ; flag: England # fully-qualified
1F466 1F3FB ; Basic_Emoji ; boy: light skin tone # fully-qualified

📌 Ключевые термины в status:

  • fully-qualified — полная, каноническая форма (все модификаторы и VS присутствуют).
  • minimally-qualified — можно опустить FE0F или skin tone (но тогда fallback).
  • unqualified — неофициальная, но допустимая (например, 👩‍❤️‍👨 без FE0F на ❤ — 2764 вместо 2764 FE0F).
  • component — только часть (например, 1F3FB сам по себе — не эмодзи, а модификатор).

📎 RGI = Recommended for General Interchange — последовательности, которые должны поддерживаться всеми платформами, претендующими на совместимость.


5. Формат emoji-test.txt — для тестирования рендереров

Этот файл — «истина в последней инстанции» для разработчика, создающего валидатор или рендерер.

Структура:

# group: Smileys & Emotion
# subgroup: face-smiling

1F600 ; fully-qualified # 😀 E0.6 grinning face
1F603 ; fully-qualified # 😃 E0.6 grinning face with big eyes
1F604 ; fully-qualified # 😄 E0.6 grinning face with smiling eyes
1F601 ; fully-qualified # 😁 E0.6 beaming face with smiling eyes
1F606 ; fully-qualified # 😆 E0.6 grinning squinting face

# subgroup: face-affection

1F60D ; fully-qualified # 😍 E0.6 smiling face with heart-eyes
1F970 ; fully-qualified # 🥰 E11.0 smiling face with 3 hearts

💡 Используется для:

  • Генерации тестовых наборов (например, в ICU, emoji-picker библиотеках)
  • Проверки покрытия поддержки эмодзи в вашем стеке
  • Автоматического извлечения коротких имён (grinning face) для i18n (CLDR)

6. Как программно работать с данными

6.1. Парсинг диапазонов

Пример на Python (без внешних зависимостей):

def parse_emoji_data_line(line: str) -> tuple[list[int], set[str], str]:
if not line or line.startswith('#'):
return [], set(), ''
parts = line.split('#')[0].strip().split(';')
if len(parts) < 2:
return [], set(), ''
codepoints_str = parts[0].strip()
props = {p.strip() for p in parts[1].split()}
# Разбор диапазона или одиночного кодпойнта
if '..' in codepoints_str:
start, end = codepoints_str.split('..')
cps = list(range(int(start, 16), int(end, 16) + 1))
else:
cps = [int(codepoints_str, 16)]
return cps, props, line

6.2. Валидация эмодзи-кластера

Псевдокод алгоритма (на основе UTS #51, Annex A):

function isEmojiGraphemeCluster(sequence: List[int]) → bool:
# 1. Разбить на Unicode grapheme clusters (по правилам UAX #29)
clusters = segmentIntoGraphemeClusters(sequence)

for cluster in clusters:
if cluster matches any of:
- [Emoji=Yes & Emoji_Presentation=Yes]
- [Emoji=Yes] + [FE0F]
- [Emoji_Modifier_Base=Yes] + [Emoji_Modifier=Yes]
- [Basic_Emoji] + [ZWJ] + [Emoji_Component=Yes]+
- [Regional_Indicator]+ (длина 2)
- [Tag_Base] + [Tag_Spec]* + [Cancel_Tag]
then continue
else:
return false
return true

📚 Источник: UTS #51, Annex A: Emoji Grapheme Cluster Definition

6.3. Нормализация в fully-qualified форму

  • Добавить FE0F к символам с Emoji=Yes && Emoji_Presentation=Yes && Variation_Selector absent
  • Для skin-tone: если база поддерживает, и модификатор в диапазоне, сохранить как есть
  • Для ZWJ: проверить по emoji-sequences.txt, есть ли exact match; если нет — fallback на отдельные эмодзи

7. Статистика по Unicode 15.1 (цифры из файлов)

КатегорияКоличество
Кодпойнтов с Emoji=Yes1 424
Из них с Emoji_Presentation=Yes1 251
Emoji_Modifier_Base=Yes112
Emoji_Modifier=Yes5 (U+1F3FB–U+1F3FF)
Emoji_Component=Yes17 (включая hair, gender, object parts)
ZWJ-sequences (все)1 237
Из них fully-qualified952
Flags (RGI)270 (256 стран + 14 субъектов)
Keycap sequences12 (0-9, *, #)

🔢 Общее число графем (emoji grapheme clusters) = сумма всех fully-qualified последовательностей + базовые = 3 782
(Данные: emoji-test.txt: строк fully-qualified = 3 782 на 2024-09-05)


📚 Часть 3: Рендеринг и платформенные различия

Как одни и те же коды отображаются в разных системах — технические причины, fallback-логика, доступность

Эта часть описывает, почему один и тот же эмодзи может выглядеть по-разному на разных устройствах и в каких случаях это приводит к неоднозначности или ошибкам. Акцент — на инженерные аспекты: шрифты, рендереры, версии ОС, поведение fallback, размеры и метрики, поддержка accessibility.


1. Основные факторы различий в отображении

ФакторОписаниеПример последствий
ШрифтЭмодзи не встроены в Unicode — они реализуются через шрифты (обычно цветные: COLR/CPAL, SVG-in-OpenType, CBDT/CBLC, sbix).На Linux без fonts-noto-color-emoji — квадраты или чёрно-белые символы.
Версия шрифтаПроизводители выпускают обновления ежегодно (вместе с новой версией Unicode Emoji).🫠 U+1FAE0 (melting face) появился в iOS 15.4 (март 2022), но на Windows 10 до 22H2 — fallback.
Рендерер ОСWindows (DirectWrite), macOS/iOS (Core Text), Android (FreeType + Skia), Linux (Pango + HarfBuzz) по-разному обрабатывают ZWJ-sequences и variation selectors.👩‍❤️‍💋‍👨 на Windows 10 (до 21H2) → 👩❤💋👨 (распадается), на iOS 17 — единый кластер.
Версия Unicode в библиотеке ICUICU (International Components for Unicode) управляет segmenter'ами, collation, date formatting, и участвует в определении emoji boundaries.Intl.Segmenter в Node.js 18+ использует ICU ≥70; ранее — мог игнорировать skin tone как отдельный кластер.
Поддержка графемных кластеровНекоторые старые редакторы/движки (например, Vim до 8.2) разбивают ZWJ-sequences на отдельные символы при подсчёте длины.👩‍💻.length в JS = 5 (кодпойнтов), но 1 (графем).

2. Основные шрифты-носители эмодзи

ПлатформаШрифтТехнология цветаГод первого релизаПоддержка Unicode 15.1
AppleApple Color Emojisbix (bitmap)2008 (iPhone OS)Полная (iOS 17.4+, macOS 14.4+)
GoogleNoto Color EmojiCBDT/CBLC (bitmap) + COLRv1 (начиная с 2022)2013Полная (Android 14 QPR2+, Chrome ≥113)
MicrosoftSegoe UI EmojiCOLR/CPAL (OpenType 1.9)2015 (Windows 8.1)Частичная: отсутствуют subdivision flags, некоторые ZWJ-sequences (например, 🫱🏻‍🫲🏿 — handshake с разным skin tone)
Linux (Google)Noto Color Emoji (open-source)CBDT, позже COLRv12016Полная (при установке v2.035+, apt install fonts-noto-color-emoji)
Linux (Mozilla)Twemoji MozillaSVG-in-OpenType (устаревший)Неполная, deprecated
Twitter/XTwemojiSVG (встраивается в DOM как <img>)2014Полная, но с собственным дизайном (отличается от системного)

📌 Колонка «Поддержка Unicode 15.1» означает:

  • Все 3 782 fully-qualified sequences отображаются как единый кластер
  • Skin tone, gender, hair style применяются корректно
  • Subdivision flags (🏴󠁧󠁢󠁥󠁮󠁧󠁿) поддерживаются

⚠️ Windows — главный «отстающий»:

  • Windows 10 (21H2): поддерживает только до Unicode 13.1 (2020)
  • Windows 11 22H2: Unicode 14.0
  • Windows 11 23H2: Unicode 15.0
  • Windows 11 24H2 (ожидается 2024 Q4): Unicode 15.1

3. Fallback-логика при отсутствии поддержки

Когда рендерер не распознаёт последовательность, применяется стандартная стратегия постсимвольного fallback (UTS #51, §2.6):

  1. Попытка отобразить как единый кластер (например, 👩‍💻)
  2. Если неизвестна ZWJ-sequence → разбить по ZWJ: 👩 + 💻
  3. Если 💻 не имеет Emoji_Presentation=Yes и нет FE0F → отобразить как текст (чёрно-белый глиф или системный символ)
  4. Если кодпойнт вообще неизвестен → замена на U+FFFD () или прямоугольник □

Пример: 🧑‍🦰‍➡️‍♂️ (U+1F9D1 U+200D U+1F9B0 U+200D U+27A1 U+FE0F U+200D U+2642 U+FE0F)
— person, red hair, arrow, male

ПлатформаРезультат (визуально)Причина
iOS 17.4🧑‍🦰‍➡️‍♂️ (единый кластер)Полная поддержка
Android 13🧑‍🦰➡️♂️ (4 части)ZWJ после hair style не распознаётся
Windows 10 21H2🧑🦰➡️♂ (распад, FE0F проигнорирован)Отсутствие Emoji_Presentation для и без VS
Chrome 100 + Noto🧑‍🦰‍➡️‍♂️COLRv1 + актуальный Noto

4. Размеры и метрики эмодзи

Эмодзи — не моноширинные, и их размер зависит от:

  • Шрифта
  • Уровня масштабирования системы (DPI scaling)
  • Контекста (внутри <span>, <button>, <textarea>)

Типичные размеры (в px, при 16pt, 96 DPI):

ЭмодзиAppleGoogleMicrosoftПримечание
😀 U+1F60024×2424×2420×20Microsoft — компактнее
🧑‍🚀 U+1F9D1 200D 1F68028×2828×2824×24ZWJ-sequences часто шире
🏴󠁧󠁢󠁥󠁮󠁧󠁿24×18Subdivision flags — прямоугольные (флаги летают на ветру)
0️⃣ U+0030 FE0F 20E320×2620×2618×24Вертикально вытянуты (keycaps)

📏 Важно для вёрстки:

  • line-height может «прыгать», если в строке есть эмодзи разной высоты
  • В textarea/input — курсор может «застревать» внутри ZWJ-кластера
  • В CSS: font-size: 1em → эмодзи масштабируется как текст, но не как 1em × 1em square, если не задано aspect-ratio: 1/1

Рекомендация для кросс-платформенной верстки:

.emoji-safe {
font-family: "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", "Twemoji Mozilla", sans-serif;
font-size: 1em;
line-height: 1.2; /* предотвращает переполнение */
display: inline-block; /* для контроля размера */
vertical-align: text-bottom; /* выравнивание по базовой линии */
}

5. Доступность (Accessibility)

Эмодзи — не замена тексту. Для пользователей скринридеров критична корректная озвучка.

Как это работает:

ТехнологияМеханизм
Apple VoiceOverИспользует CLDR short names из emoji-test.txt (например, «grinning face»). Поддерживает skin tone: «woman: medium skin tone».
Google TalkBackАналогично, но иногда упрощает: «couple with heart» вместо «woman, heart, man».
NVDA + WindowsЗависит от ICU и системного шрифта. До Windows 11 23H2 — часто «unknown symbol» для новых эмодзи.
JAWSТребует обновления словаря; по умолчанию — «emoji» или код («U+1F600»).

Рекомендации для разработчиков:

Делайте:

  • Добавляйте aria-label при использовании эмодзи как иконки:
    <span aria-label="Ошибка: проверьте ввод" role="img"></span>
  • Используйте <img alt="grinning face"> для критичных случаев (например, в email)
  • Избегайте эмодзи в aria-hidden="true" контенте

Не делайте:

  • aria-label="😀" — скринридер произнесёт «U+1F600» или «grinning face», но это избыточно
  • Замену текста на эмодзи в интерфейсе («Отправить ✉️» → лучше «Отправить письмо ✉️»)

📊 Исследование WebAIM (2024):
68% пользователей скринридеров сообщают, что «эмодзи мешают пониманию», если не сопровождаются текстом.


6. Анимация и интерактивность

Некоторые платформы поддерживают анимированные эмодзи:

ЭмодзиПлатформаПоведение
😂 U+1F602iOS 17+, macOS 14+При long-press в Messages — слёзы падают
🎉 U+1F389Telegram DesktopАнимированная конфетти-частица (SVG + CSS)
❤️ U+2764 FE0FWhatsApp WebПульсация при реакции

🛠 Технически:

  • В мобильных ОС — встроенная анимация через Core Animation / Android Drawable
  • В вебе — только кастомная реализация (Twemoji + CSS @keyframes)
  • Стандарт Unicode не регулирует анимацию — это эксклюзив платформы

7. Практические таблицы совместимости

7.1. Поддержка ключевых новых эмодзи (Unicode 15.0–15.1)

ЭмодзиИмяКодiOSAndroidWindowsLinux (Noto)Замечания
🫠melting face1FAE015.4+13+11 23H2+2.030+На Windows 10 — □
🫨shaking face1FAE816.0+14+11 24H2 (ожид.)2.035+
🫰hand with index finger and thumb crossed1FAF016.4+14 QPR22.035+В Windows — fallback на ✌️
🫱🏻‍🫲🏿handshake: light & dark skin1FAF1 1F3FB 200D 1FAF2 1F3FF16.4+14 QPR22.035+На Windows — 👋🏻🤝🏿 (3 кластера)
🪩mirror ball1FAA916.0+14+11 23H2+2.033+

7.2. Проблемные ZWJ-sequences (частые точки отказа)

ПоследовательностьОжидаемоеWindows 11 22H2Android 12iOS 15.0
👩‍❤️‍💋‍👨couple kiss (W-W-M)👩❤💋👨👩‍❤️‍💋‍👨👩‍❤️‍💋‍👨
🧑‍🤝‍🧑handshake (neutral)🤝🤝🤝
🧑‍🤝‍🧑🏻handshake + skin🤝🏻🤝 (игнорирует skin)🤝 (игнорирует skin)
🏴󠁧󠁢󠁥󠁮󠁧󠁿flag: England□□□□□□□□□□□□□□🏴󠁧󠁢󠁥󠁮󠁧󠁿

🔍 Примечание:

  • Skin tone в handshake (🧑‍🤝‍🧑🏻) — технически invalid sequence по emoji-sequences.txt (требуется оба участника иметь skin tone: 🧑🏻‍🤝‍🧑🏿).
  • Поэтому fallback корректен — стандарт не гарантирует отображение неканонических вариантов.

📚 Часть 4: Эмодзи в инфраструктуре

Хранение, обработка, поиск и совместимость в СУБД, API, JSON, файловых системах

Эта часть посвящена практическим аспектам эксплуатации эмодзи в IT-системах: как избежать потери данных, ошибок кодировки, некорректного поиска и проблем с миграцией. Акцент — на конкретные решения для MySQL, PostgreSQL, SQLite, JSON, UTF-8, а также на нормализацию и колляции.


1. Кодировка и представление в UTF-8

Эмодзи — это не «спецсимволы», а полноценные Unicode-кодпойнты. Однако их размер в UTF-8 варьируется:

ТипПримерКодпойнтUTF-8 (байты)Примечание
Базовый (BMP)©U+00A9C2 A9 (2)Не эмодзи без VS-16
Базовый (Astral)😀U+1F600F0 9F 98 80 (4)Все эмодзи ≥ U+10000 — 4 байта
Skin tone👋🏽U+1F44B U+1F3FDF0 9F 91 8B F0 9F 8F BD (8)2 × 4 байта
ZWJ-sequence👩‍💻U+1F469 U+200D U+1F4BBF0 9F 91 A9 E2 80 8D F0 9F 92 BB (11)4 + 3 + 4 = 11 байт
Subdivision flag🏴󠁧󠁢󠁥󠁮󠁧󠁿7 кодпойнтов28 байтF0 9F 8F B4 F3 A0 81 A7 ...

⚠️ Критичные ограничения:

  • MySQL 5.5–5.7 (utf8): поддерживает только 3 байта → эмодзи обрезаются или заменяются на ???.
  • MySQL 8.0+ (utf8mb4): 4 байта — работает.
  • PostgreSQL (UTF8): всегда 4 байта — корректно.
  • SQLite (UTF-8): корректно, но LENGTH() считает байты, а не символы.

2. СУБД: сравнительная таблица поддержки

СУБДВерсия minКодировкаCOLLATIONLENGTH()SUBSTRING()FULLTEXT поискЗамечания
MySQL5.5.3+utf8mb4utf8mb4_unicode_520_ci (Unicode 5.2) utf8mb4_0900_ai_ci (Unicode 9.0)байтыпо байтам (если не CHAR)Нет для эмодзи (до 8.0.19)SET NAMES utf8mb4 обязателен. utf8 — ловушка.
PostgreSQL9.1+UTF8en_US.utf8, C.UTF-8, und-x-icu (с ICU)символы (code points)по code pointsДа (через pg_trgm + unaccent)CHAR_LENGTH() = grapheme ≠ code points.
SQLite3.0+UTF-8Нет (binary или NOCASE)байтыпо байтамНетИспользуйте length(unicode_string) в Python/JS, а не SQL-LENGTH().
SQL Server2012+UTF-8 (с 2019), ранее NVARCHAR (UTF-16)Latin1_General_100_CI_AS_SC_UTF8символы (UTF-16 code units)по code unitsЧастичноSC (Supplementary Characters) обязателен.

📌 Ключевые термины:

  • SC (Supplementary Characters) — поддержка surrogate pairs (UTF-16) или astral plane (UTF-8)
  • AI (Accent Insensitive), CI (Case Insensitive) — не влияют на эмодзи (они не имеют регистра/ударения)
  • ICU collation — использует Intl.Segmenter-подобную логику (PostgreSQL ≥12)

3. Практические проблемы и решения

3.1. Обрезка эмодзи при SUBSTRING

Проблема:

-- MySQL, utf8mb4
SELECT SUBSTRING('👋🏽', 1, 4); -- возвращает первые 4 *байта*: F0 9F 91 8B → 👋 (без skin tone)

Но 👋🏽 = 8 байт. Обрезка на 4 байтах — некорректный UTF-8, и клиент может показать .

Решение:

  • В MySQL 8.0+:
    SELECT SUBSTRING('👋🏽' USING utf8mb4); -- не помогает, всё равно по байтам
    SELECT LEFT('👋🏽', 1); -- возвращает 👋 (первый кодпойнт), не кластер
  • В приложении: используйте grapheme-aware функции:
    • Python: import regex; regex.findall(r'\X', s)
    • JS: [...s] — нет! Используйте Intl.Segmenter:
      const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
      const segments = [...segmenter.segment('👋🏽')];
      segments[0].segment; // '👋🏽'

3.2. Поиск эмодзи

Проблема:

-- MySQL
SELECT * FROM comments WHERE text LIKE '%😀%'; -- работает, если кодировка utf8mb4
SELECT * FROM comments WHERE text LIKE '%👩‍💻%'; -- может не найти, если в БД хранится без FE0F

Решения:

МетодPostgreSQLMySQL 8.0+Примечание
Прямой поискWHERE text LIKE '%😀%'То жеТребует точного совпадения последовательности
Поиск по базеWHERE text ~* '\x{1F469}\x{200D}\x{1F4BB}'REGEXP '👨‍💻'Регулярки поддерживают Unicode escapes
Поиск игнорируя skin toneНет в SQL. Требует нормализации в приложенииСм. ниже: «Нормализация»

3.3. Индексация и сортировка

  • MySQL: utf8mb4_0900_ai_ci — сортирует эмодзи по code point (😀 < 😁 < 😂), но не по смыслу.
  • PostgreSQL с ICU: CREATE COLLATION "emoji" (provider = icu, locale = 'und-u-kr-emoji'); — сортировка по порядку в emoji-test.txt (группы → подгруппы).
  • Практика: не сортируйте по полям с эмодзи. Используйте отдельное sort_order INT для UI.

4. Нормализация эмодзи (NFC/NFD и beyond)

Unicode нормализация (NFC, NFD) не влияет на эмодзи, так как они не имеют combining marks в обычном смысле. Однако для эмодзи существуют свои правила:

4.1. Canonical Equivalence для эмодзи

Неканоническая формаКаноническая (fully-qualified)Комментарий
U+2764 (❤)U+2764 U+FE0F (❤️)Добавление VS-16
U+1F469 U+200D U+2764 U+200D U+1F468U+1F469 U+200D U+2764 U+FE0F U+200D U+1F468Добавление VS-16 к ❤
U+1F466 (👦)U+1F466 U+1F3FB (👦🏻) — нет!Skin tone — не нормализация, а семантическое расширение

📜 Стандарт: UTS #51, §2.5: Emoji Normalization

4.2. Алгоритм нормализации в коде

Псевдокод (на основе emoji-variation-sequences.txt):

def normalize_emoji(s: str) -> str:
# 1. Найти все кластеры (grapheme segmentation)
clusters = segment_into_grapheme_clusters(s)
result = []
for cluster in clusters:
# 2. Проверить, есть ли exact match в emoji-sequences.txt (fully-qualified)
if cluster in fully_qualified_sequences:
result.append(cluster)
# 3. Иначе — попробовать добавить FE0F к базовым Emoji_Presentation=Yes
elif is_basic_emoji_without_vs(cluster):
base = cluster[0]
if has_property(base, 'Emoji_Presentation'):
result.append(codepoint_to_str(base) + '\uFE0F')
else:
result.append(cluster)
else:
result.append(cluster)
return ''.join(result)

🔍 Реализации:


5. JSON и эмодзи

5.1. Сериализация

  • JSON RFC 8259 требует UTF-8, UTF-16 или UTF-32. Эмодзи допустимы без экранирования.
  • Пример корректного JSON:
    {
    "message": "Привет, 🌍! Ты набрал 100 🪙.",
    "reactions": ["👍", "❤️", "🚀"]
    }

5.2. Проблемы

ПроблемаПричинаРешение
UnicodeEncodeError в Python 2json.dumps() по умолчанию в ASCIIjson.dumps(obj, ensure_ascii=False)
Invalid \uXXXX escape в парсереЭмодзи сохранены как \uD83D\uDE00 (UTF-16 surrogate)Используйте UTF-8 end-to-end. Избегайте JSON_UNESCAPED_UNICODE в PHP без проверки кодировки.
Многострочные ZWJ в minified JSON\u200D после минификации может слитьсяНе минифицируйте JSON как строку — используйте AST-ориентированные инструменты (например, jq --compact-output)

5.3. Размеры

СтрокаДлина (символы)Размер UTF-8 (байт)JSON-сериализация (без ensure_ascii=False)
"😀"14"\\ud83d\\ude00" → 14 байт
"😀" с ensure_ascii=False"😀" → 6 байт (", 4 байта, ")
"👩‍💻"5 (code points)11"\ud83d\udc69\u200d\ud83d\udcbb" → 38 байт

📊 Вывод: всегда используйте ensure_ascii=False (Python), JSON_UNESCAPED_UNICODE (PHP ≥5.4), JSON.stringify() в Node.js (по умолчанию UTF-8).


6. Файловые системы и ОС

ОС / ФСПоддержка имён файлов с эмодзиЗамечания
Windows NTFSНо: в CMD/PowerShell до 5.1 — отображение как . Требуется chcp 65001.
macOS APFS/HFS+Используется NFD-нормализация — é = e + ◌́, но эмодзи не затрагиваются.
Linux ext4/xfsЗависит от локали терминала (LC_CTYPE=en_US.UTF-8).
ZIP-архивы⚠️Стандарт ZIP не указывает кодировку. Используйте флаг 0x0800 (EFS — UTF-8) или Info-ZIP convention.

🛠 Проверка в Linux:

touch "🚀_launch.txt"
ls | hexdump -C # убедитесь, что F0 9F 9A 80 присутствует

7. Миграции и backward compatibility

Сценарий: переход с MySQL 5.7 (utf8) на 8.0 (utf8mb4).

Риски:

  • Существующие эмодзи в utf8 — уже повреждены (??? или обрезаны).
  • ALTER TABLE CONVERT TO CHARACTER SET utf8mb4 не восстановит данные.

Алгоритм безопасной миграции:

  1. Проверьте целостность:
    SELECT * FROM comments WHERE HEX(text) LIKE '%EFBFBD%'; -- U+FFFD = 
  2. Сделайте дамп в UTF-8 (не в utf8 MySQL!):
    mysqldump --default-character-set=utf8mb4 --hex-blob db table > dump.sql
  3. Импортируйте в новую БД с utf8mb4 и utf8mb4_0900_ai_ci.
  4. Протестируйте на sample-данных с:
    • 😀, 👩‍💻, 👋🏽, 0️⃣, 🏴󠁧󠁢󠁥󠁮󠁧󠁿

📌 Для PostgreSQL:

  • pg_dump --encoding=UTF8 --no-owner --no-privileges
  • Перед восстановлением: CREATE DATABASE db ENCODING 'UTF8' LC_COLLATE 'C.UTF-8';

📚 Часть 5: Безопасность и уязвимости

Эмодзи как вектор атак: XSS, homograph-спуфинг, фишинг, спам, модерация

Эта часть посвящена практическим рискам, связанным с использованием эмодзи в веб- и мобильных приложениях, а также методам их предотвращения. Акцент — на воспроизводимые exploit-сценарии, механизмы срабатывания и проверенные контрмеры.


1. XSS и инъекции через эмодзи

Эмодзи не являются исполнимым кодом, но могут участвовать в обходе фильтров.

1.1. Обфускация payload через эмодзи в текстовом контексте

Сценарий:
Фильтр разрешает «только буквы, цифры и эмодзи», но не нормализует последовательности.

Exploit:

<!-- Ввод пользователя: -->
<button title="Click me 👁️‍🗨️">Submit</button>

<!-- Если backend некорректно парсит ZWJ: -->
<button title="Click me 👁️‍🗨️ onerror=alert(1)">Submit</button>

Как это работает:

  • 👁️‍🗨️ = U+1F441 U+FE0F U+200D U+1F5E8 U+FE0F
  • Некоторые парсеры (особенно на основе регулярных выражений) могут разбить ZWJ-sequence и вставить атрибут между 👁️ и 🗨️:
    <button title="Click me 👁️" onerror="alert(1)" 🗨️>Submit</button>

Уязвимые стеки:

  • Устаревшие версии DOMPurify (<2.4.0) без USE_PROFILES: { svg: true }
  • Кастомные WAF-правила, использующие \p{Emoji} без проверки на ZWJ integrity
  • innerHTML в браузерах с неполной поддержкой ZWJ (IE11, старый Edge)

Контрмеры:
✅ Валидируйте графемные кластеры, а не code points:

// Проверка: вся строка состоит из эмодзи-графем + разрешённых символов
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
const isValid = [...segmenter.segment(input)].every(seg =>
seg.isEmoji || /[a-zA-Z0-9\s.,!?]/.test(seg.segment)
);

✅ Используйте DOMPurify.sanitize(input, { ALLOWED_TAGS: [], ALLOWED_ATTR: [] }) — он корректно обрабатывает ZWJ.


1.2. Эмодзи в URL и SSRF

Сценарий:
Пользователь вводит URL с эмодзи: https://api.example.com/fetch?url=https://тест.рф/🚀

Проблема:

  • URL-парсеры могут декодировать 🚀 как U+1F680, но HTTP-клиент не экранирует его в GET /🚀 HTTP/1.1
  • Сервер может интерпретировать 🚀 как часть пути и передать в curl/HttpClient без percent-encoding.

Риск:

  • Если backend использует exec('curl ' + url), возможна command injection через $(id) в домене (но не через эмодзи напрямую).
  • Основная угроза — некорректный логгинг: 🚀 в логах может нарушать парсинг (например, Logstash падает при не-UTF8).

Контрмеры:
✅ Всегда percent-encode non-ASCII в URL:

encodeURI('https://тест.рф/🚀') // → 'https://xn--e1aybc.xn--p1ai/%F0%9F%9A%80'

✅ Валидируйте URL через new URL(input) — он выбросит, если последовательность недекодируема.


2. Homograph-атаки и визуальный спуфинг

Эмодзи могут визуально имитировать интерфейсные элементы.

2.1. Подмена статусов и иконок

ЭмодзиВизуальный эффектРиск
🔒➡️🔓«Замок → разблокировано»Фишинг: «Ваш аккаунт разблокирован! Нажмите 🔓»
✅➡️❌Смена статусаПодделка уведомлений: «Доставка ❌» (на самом деле ✅)
👁️‍🗨️«Глаз в речевом пузыре»Имитация «просмотрено» в мессенджерах
0️⃣ vs OЦифра 0 vs латинская OПодделка серийных номеров, лицензий

Доказательство концепции (Telegram):
Сообщение:

«Ваша сессия 🔒. Перейдите по ссылке для подтверждения: [ссылка] 🔓»
→ Пользователь думает, что сессия заблокирована, хотя на деле — защищена.

Контрмеры:
✅ Запретите эмодзи в критичных полях: email, логин, номер договора.
✅ В UI используйте SVG-иконки вместо эмодзи для статусов.
✅ Добавьте aria-label к эмодзи:

<span aria-label="Статус: защищено" role="img">🔒</span>

2.2. Emoji Domain Names (Punycode + эмодзи)

Хотя IDN не разрешает эмодзи напрямую, возможна комбинация:

  • xn--e1aybc.xn--p1ai (тест.рф) + эмодзи в пути/фрагменте
  • Но: браузеры блокируют эмодзи в домене (Chrome, Firefox, Safari).

Проверка:

new URL('https://🚀.example.com') // → TypeError: Invalid URL

Исключение:

  • Старые версии браузеров (Safari ≤12) допускали https://xn--🚀/ — уязвимость закрыта в 2019.

3. Фишинг и социальная инженерия

3.1. Поддельные аватарки

Сценарий:
Атакующий использует 👩‍💼➡️👨‍💼 в имени профиля:

«Поддержка 👩‍💼➡️👨‍💼 (смена менеджера)»

→ Пользователь видит «женщина-менеджер стала мужчиной-менеджером» и доверяет.

Реальные кейсы:

  • Telegram-каналы с ⚠️Официально⚠️ в названии
  • Discord-серверы с ✅ Verified через эмодзи

Контрмеры:
✅ В UI обрезайте отображаемое имя до 20 символов графем, а не code points.
✅ Для верифицированных аккаунтов — отдельный badge, не эмодзи.


3.2. Эмодзи в SMS и USSD

Проблема:

  • USSD-коды (*100#) не поддерживают эмодзи.
  • Но: *100#️⃣ (keycap sequence) может отправиться как *100# + U+FE0F U+20E3 → оператор отбрасывает «лишнее», и запрос проходит.

Риск:

  • Если SMS-шлюз не фильтрует эмодзи, возможна отправка *100#️⃣💸 → интерпретируется как *100# + текст (но USSD игнорирует пост-хэш-часть).

Контрмеры:
✅ В SMS-валидаторе удаляйте всё после #:

ussd = re.sub(r'#.*$', '#', user_input)

4. Спам и контент-модерация

4.1. Обход спам-фильтров

Техники:

  • Вставка эмодзи между буквами: V🩸I🩸A🩸G🩸R🩸A
  • Замена букв на визуально похожие эмодзи: 👁️ вместо I, 🅰️ вместо A

Эффективность:

  • Простые regex-фильтры (/viagra/i) не срабатывают.
  • ML-модели (например, TensorFlow Serving с BERT) требуют fine-tuning на эмодзи-датасетах.

Датасеты для обучения:

  • Emoji-Attacker (UC Irvine, 2023)
  • Собирайте логи: SELECT text FROM spam_reports WHERE text ~* '\p{Emoji}';

4.2. Модерация через API

СервисПоддержка эмодзиПримечание
Google Perspective APIОценивает toxicity даже в 🖕➡️👍 как high
Azure Content ModeratorКлассифицирует 🔞 как adult content
Amazon Rekognition TextРаспознаёт 💊 как drug-related
Локальные решения (VADER, TextBlob)Игнорируют эмодзи без адаптации

Рекомендация:

  • Для локальной модерации используйте emoji (Python) + словарь:
    toxic_emojis = {'🖕', '🤬', '💀', '💊', '🔞'}
    if any(e in toxic_emojis for e in emoji.distinct_emoji_list(text)):
    flag_as_toxic()

5. Приватность и отслеживание

5.1. Emoji Fingerprinting

Механизм:

  • Браузер отображает эмодзи через шрифт → можно измерить ширину/высоту через <canvas>:
    const ctx = canvas.getContext('2d');
    ctx.font = '32px "Segoe UI Emoji", "Apple Color Emoji"';
    const width = ctx.measureText('😀').width;
    // width = 36 на Windows, 42 на macOS → fingerprint

Защита:
✅ В Tor Browser и Firefox с privacy.resistFingerprinting=true — фиксированные размеры эмодзи.
✅ Для веб-приложений — не используйте measureText() для эмодзи.


6. Таблица уязвимостей и контрмер

УгрозаCWEУровень рискаКонтрмера
XSS через ZWJ-разрывCWE-79MediumГрафемная сегментация, DOMPurify ≥2.4.0
Homograph-спуфингCWE-297HighЗапрет эмодзи в логинах/URL, aria-label
Фишинг через статусыCWE-353MediumSVG-иконки вместо эмодзи в UI
Обход спам-фильтровCWE-116LowML-модели + emoji-словари
Emoji fingerprintingCWE-200LowОтказ от measureText() для эмодзи

📌 CWE — Common Weakness Enumeration.
Риск оценен по методике CVSS 3.1:

  • High: прямое влияние на конфиденциальность/целостность (подделка статуса → финансовые потери)
  • Medium: требует user interaction (нажать на фишинговую ссылку)
  • Low: сбор метаданных без прямого ущерба